//--------------------------------------------------------------------------------
// This file is a portion of the PracticeEngine Rendering Engine.  It is distributed
// under the MIT License, available in the root of this distribution and 
// at the following URL:
//
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright (c) Ferdinand Li 
//--------------------------------------------------------------------------------
#include "PCH.h"
#include "ModelLoader.h"
#include "Logger.h"
#include <map>
#include <vector>
#include <sstream>
#include <assert.h>
using namespace PracEng;
#pragma warning(push)
#pragma warning(disable:4244)
#pragma warning(disable:4996)

void StringToWstring(std::wstring& szDst, std::string str)
{
	std::string temp = str;
	int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, NULL, 0);
	wchar_t * wszUtf8 = new wchar_t[len + 1];
	memset(wszUtf8, 0, len * 2 + 2);
	MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, (LPWSTR)wszUtf8, len);
	szDst = wszUtf8;
	std::wstring r = wszUtf8;
	delete[] wszUtf8;
}

struct MtlInfo
{
	std::string DiffuseMapName;
	std::string NormalMapName;
};
///////////////////////////////////////////
class CBaseIndex
{
public:
	unsigned int m_dwPosNo;				//!< 0..
	unsigned int m_dwNormNo;				//!< 0..
};

struct CBaseIndexOrder : public std::binary_function < CBaseIndex, CBaseIndex, bool >
{
	bool operator() (const CBaseIndex &a, const CBaseIndex &b) const
	{
		// first sort by position
		if (a.m_dwPosNo < b.m_dwPosNo)return true;
		if (a.m_dwPosNo > b.m_dwPosNo)return false;

		// then by normal
		if (a.m_dwNormNo < b.m_dwNormNo)return true;
		if (a.m_dwNormNo > b.m_dwNormNo)return false;

		return false;
	}
};

class CBase33
{
public:

	CBase33() { }
	CBase33(PEVector3 Uval, PEVector3 Vval, PEVector3 Nval) { u = Uval; v = Vval; n = Nval; }

	PEVector3 u;								//!<
	PEVector3 v;								//!<
	PEVector3 n;								//!< is part of the tangent base but can be used also as vertex normal
};

class CTriBaseIndex
{
public:
	unsigned int p[3];							//!< index in m_BaseVectors 
};

std::vector<CTriBaseIndex>						m_TriBaseAssigment;							//!< [0..dwTriangleCount]
std::vector<CBase33>							m_BaseVectors;								//!< [0..] generated output data

unsigned int AddUV2Base(std::multimap<CBaseIndex, unsigned int, CBaseIndexOrder> &inMap, const unsigned int indwPosNo, const unsigned int indwNormNo, const PEVector3 &inU, const PEVector3 &inV, const PEVector3 &inNormN)
{
	CBaseIndex Indx;

	Indx.m_dwPosNo = indwPosNo;
	Indx.m_dwNormNo = indwNormNo;

	std::multimap<CBaseIndex, unsigned int, CBaseIndexOrder>::iterator iFind, iFindEnd;

	iFind = inMap.lower_bound(Indx);

	//assert(iFind != inMap.end());

	PEVector3 vNormal = m_BaseVectors[(*iFind).second].n;

	iFindEnd = inMap.upper_bound(Indx);

	unsigned int dwBaseUVIndex = 0xffffffff;													// init with not found

	bool bParity = (PEVector3::Cross(inU, inV).Dot(inNormN) > 0.0f);

	for (; iFind != iFindEnd; ++iFind)
	{
		CBase33 &refFound = m_BaseVectors[(*iFind).second];

		if (!refFound.u.IsZero())
		{
			bool bParityRef = (PEVector3::Cross(refFound.u, refFound.v).Dot(refFound.n) > 0.0f);
			bool bParityCheck = (bParityRef == bParity);

			if (!bParityCheck)
				continue;

			PEVector3 vrefUV = refFound.u + refFound.v;

			// project down to the plane (plane normal = inNormN)
			PEVector3 vRotHalf = vrefUV - inNormN*(inNormN*vrefUV);

			//			bool bHalfAngleCheck=normalize(inU+inV) * normalize(vRotHalf) > cos(fThreshold);
			bool bHalfAngleCheck = (inU + inV).Dot( vRotHalf )> 0.0f;		// length doesn't matter so normalize is not needed

			if (!bHalfAngleCheck)
				continue;
		}

		dwBaseUVIndex = (*iFind).second; break;
	}

	if (dwBaseUVIndex == 0xffffffff)														// not found
	{
		// otherwise create a new base

		CBase33 Base(PEVector3(0, 0, 0), PEVector3(0, 0, 0), vNormal);

		dwBaseUVIndex = m_BaseVectors.size();

		inMap.insert(std::pair<CBaseIndex, unsigned int>(Indx, dwBaseUVIndex));
		m_BaseVectors.push_back(Base);
	}

	CBase33 &refBaseUV = m_BaseVectors[dwBaseUVIndex];

	refBaseUV.u = refBaseUV.u + inU;
	refBaseUV.v = refBaseUV.v + inV;

	//no mesh is perfect 
	if (inU.x != 0.0f || inU.y != 0.0f || inU.z != 0.0f)
		assert(refBaseUV.u.x != 0.0f || refBaseUV.u.y != 0.0f || refBaseUV.u.z != 0.0f);
	// no mesh is perfect
	if (inV.x != 0.0f || inV.y != 0.0f || inV.z != 0.0f)
		assert(refBaseUV.v.x != 0.0f || refBaseUV.v.y != 0.0f || refBaseUV.v.z != 0.0f);

	return dwBaseUVIndex;
}
void AddNormal2Base(std::multimap<CBaseIndex, unsigned int, CBaseIndexOrder> &inMap, const unsigned int indwPosNo, const unsigned int indwNormNo, const PEVector3 &inNormal)
{
	CBaseIndex Indx;

	Indx.m_dwPosNo = indwPosNo;
	Indx.m_dwNormNo = indwNormNo;

	std::multimap<CBaseIndex, unsigned int, CBaseIndexOrder>::iterator iFind = inMap.find(Indx);

	unsigned int dwBaseNIndex;

	if (iFind != inMap.end())																// found
	{
		// resuse the existing one

		dwBaseNIndex = (*iFind).second;
	}
	else
	{
		// otherwise create a new base

		CBase33 Base(PEVector3(0, 0, 0), PEVector3(0, 0, 0), PEVector3(0, 0, 0));

		dwBaseNIndex = m_BaseVectors.size();
		inMap.insert(std::pair<CBaseIndex, unsigned int>(Indx, dwBaseNIndex));
		m_BaseVectors.push_back(Base);
	}

	CBase33 &refBaseN = m_BaseVectors[dwBaseNIndex];

	refBaseN.n = refBaseN.n + inNormal;
}


////////////////////////////////////////////////
OBJModel_Loader::OBJModel_Loader()
{

}

OBJModel_Loader::~OBJModel_Loader()
{

}

bool OBJModel_Loader::Load(std::wstring& ObjFilePath, std::wstring& MtlFilePath, std::vector<OBJModel* >& Objects)
{
	//load mesh
	std::ifstream ifs(ObjFilePath);
	if ((ifs.rdstate() & std::ifstream::failbit) != 0)
	{
		PracEngLogf("Load Obj File failed! FilePath:%s", ObjFilePath.c_str());
		return false;
	}
	std::string DataBuffer;
	char* token;
	bool bNewGeo = true;
	int LoadedVertices = 0;
	int LoadedNormal = 0;
	int LoadedUV = 0;
	int VertexOffset = 0;
	int NormalOffset = 0;
	int UVOffset = 0;
	OBJModel* pObjModel = nullptr;
	while (getline(ifs, DataBuffer))
	{
		// It's comments, skip it!
		if (DataBuffer[0] == '#')
		{
			continue;
		}
		token = strtok(&DataBuffer[0], " /");
		while (token != nullptr)
		{
			if ( strcmp("v",token) == 0 ) //vertex data
			{
				if (bNewGeo)
				{
					
					pObjModel = new OBJModel();
					if (pObjModel)
					{
						Objects.push_back(pObjModel);
					}
					bNewGeo = false;
				}

				PEFloat3 vertexPos;
				token = strtok(NULL, " ");
				vertexPos.X = atof(token);
				token = strtok(NULL, " ");
				vertexPos.Y = atof(token);
				token = strtok(NULL, " ");
				vertexPos.Z = atof(token);
				
				pObjModel->VertexBuffer.push_back(vertexPos);
				LoadedVertices++;
				break;
			}
			else if ( strcmp("vn", token) == 0 ) //vertex normal data
			{
				PEVector3 vertexNormal;
				token = strtok(NULL, " ");
				vertexNormal.x = atof(token);
				token = strtok(NULL, " ");
				vertexNormal.y = atof(token);
				token = strtok(NULL, " ");
				vertexNormal.z = atof(token);

				pObjModel->VertexNormal.push_back(vertexNormal);
				LoadedNormal++;
				break;
			}
			else if (strcmp("vt", token) == 0) //UV data
			{
				PEFloat2 UV;
				token = strtok(NULL, " ");
				UV.X = atof(token);
				token = strtok(NULL, " ");
				UV.Y = atof(token);
				UV.Y = 1.0f - UV.Y;

				pObjModel->UVs.push_back(UV);
				LoadedUV++;
				break;
			}
			else if (strcmp("g", token) == 0) //obj group
			{
				token = strtok(NULL, " ");
				pObjModel->Name = token;
				bNewGeo = true;
				bool bNeedOffset = Objects.size() > 1;
				VertexOffset = bNeedOffset ? LoadedVertices - Objects[Objects.size() - 1]->VertexBuffer.size() : 0;
				NormalOffset = bNeedOffset ? LoadedNormal - Objects[Objects.size() - 1]->VertexNormal.size() : 0;
				UVOffset = bNeedOffset ? LoadedUV - Objects[Objects.size() - 1]->UVs.size() : 0;
				PracEngLogf("Load Model: %s! \n", token);
				break;
			}
			else if (strcmp("usemtl", token) == 0) //used mtl
			{
				token = strtok(NULL, " ");
				pObjModel->MtlName = token;
				break;
			}
			else if (strcmp("f", token) == 0) //face data
			{
				while (token != nullptr)
				{
					OBJFaceInfo FI;
					token = strtok(NULL, " /");
					if (token == NULL)
						break;
					FI.VertexIdx = atoi(token);
					FI.VertexIdx -= (VertexOffset + 1);
					token = strtok(NULL, " /");
					FI.UVIdx = atoi(token);
					FI.UVIdx -= (UVOffset + 1);
					token = strtok(NULL, " /");
					FI.NormalIdx = atoi(token);
					FI.NormalIdx -= (NormalOffset + 1);
					pObjModel->FaceInfos.push_back(FI);
					pObjModel->VertexTangent.push_back(PEVector4());
				}

				break;
			}
			else
				break;
		}
		

	}

	//Redirect texture path
	ifs = std::ifstream(MtlFilePath);
	if ((ifs.rdstate() & std::ifstream::failbit) != 0)
	{
		PracEngLogf("Load mtl File failed! FilePath:%s", MtlFilePath.c_str());
		return false;
	}

	std::string currMtlName;
	std::map<std::string, MtlInfo> MtlMap;
	std::stringstream ss;
	
	ss << ifs.rdbuf();

	while (ss)
	{
		ss >> DataBuffer;
		if (DataBuffer.empty())
			break;

		if (DataBuffer.compare("newmtl") == 0)
		{
			ss >> currMtlName;
			MtlMap.insert(std::pair<std::string, MtlInfo>(currMtlName, MtlInfo()));
		}
		else if (DataBuffer.compare("map_Kd") == 0)
		{
			auto mtlData = MtlMap.find(currMtlName);
			if (mtlData != MtlMap.end() )
			{

				ss >> mtlData->second.DiffuseMapName;
			}
		}
		else if (DataBuffer.compare("bump") == 0)
		{
			auto mtlData = MtlMap.find(currMtlName);
			if (mtlData != MtlMap.end())
			{
				ss >> mtlData->second.NormalMapName;
			}
		}

	}
	if (MtlMap.size() > 0)
	{
		for (auto obj : Objects)
		{
			auto findObj = MtlMap.find(obj->MtlName);
			if (findObj != MtlMap.end())
			{
				StringToWstring(obj->DiffuseMapName, findObj->second.DiffuseMapName);
				StringToWstring(obj->NormalMapName, findObj->second.NormalMapName);
			}
		}
	}

	return true;
}

//bool OBJModel_Loader::BuildTangent(std::vector<OBJModel* >& Objects)
//{
//	for (OBJModel* actor : Objects)
//	{
//		PracEngLogf("BuildTangent For Model: %s! \n", actor->Name.c_str());
//		int cnt = actor->VertexBuffer.size(); 
//		PEVector3* tan1 = new PEVector3[cnt * 2];
//		PEVector3* tan2 = tan1 + cnt;
//		ZeroMemory(tan1, cnt*sizeof(PEVector3) * 2);
//		
//		for (size_t i = 0; i < actor->FaceInfos.size(); i+=3)
//		{
//			
//			int Idx1 = actor->FaceInfos[i].VertexIdx;
//			int Idx2 = actor->FaceInfos[i + 1].VertexIdx;
//			int Idx3 = actor->FaceInfos[i + 2].VertexIdx;
//
//			const PEFloat3& v1 = actor->VertexBuffer[Idx1];
//			const PEFloat3& v2 = actor->VertexBuffer[Idx2];
//			const PEFloat3& v3 = actor->VertexBuffer[Idx3];
//
//			const PEFloat2& w1 = actor->UVs[actor->FaceInfos[i].UVIdx];
//			const PEFloat2& w2 = actor->UVs[actor->FaceInfos[i+1].UVIdx];
//			const PEFloat2& w3 = actor->UVs[actor->FaceInfos[i+2].UVIdx];
//
//			PEVector3 E1(v2.X - v1.X, v2.Y - v1.Y, v2.Z - v1.Z);
//			PEVector3 E2(v3.X - v1.X, v3.Y - v1.Y, v3.Z - v1.Z);
//
//			PEFloat2 St1 ( w2.X - w1.X, w2.Y - w1.Y );
//			PEFloat2 St2 ( w3.X - w1.X, w3.Y - w1.Y );
//			PEVector3 sDir;
//			PEVector3 tDir;
//
//			float r = St1.X*St2.Y - St2.X*St1.Y;
//			if (fabs(r) < 1e-6f)
//			{
//				sDir = PEVector3(1.0f, .0f, .0f);
//				tDir = PEVector3(.0f, 1.0f, .0f);
//			}
//			else
//			{
//				r = 1.0f / r;
//
//				sDir = PEVector3((St2.Y*E1.x - St1.Y*E2.x)*r, (St2.Y*E1.y - St1.Y*E2.y)*r, (St2.Y*E1.z - St1.Y*E2.z)*r);
//				tDir = PEVector3((St1.X*E2.x - St2.X*E1.x)*r, (St1.X*E2.y - St2.X*E1.y)*r, (St1.X*E2.z - St2.X*E1.z)*r);
//			}
//
//			tan1[Idx1] += sDir;
//			tan1[Idx2] += sDir;
//			tan1[Idx3] += sDir;
//
//			tan2[Idx1] += tDir;
//			tan2[Idx2] += tDir;
//			tan2[Idx3] += tDir;
//
//		}
//
//		for (size_t i = 0; i < actor->FaceInfos.size(); i++)
//		{
//			int Idx = actor->FaceInfos[i].VertexIdx;
//			const PEVector3& N = actor->VertexNormal[actor->FaceInfos[i].NormalIdx];
//			const PEVector3& T = tan1[Idx];
//			PEVector3 adjTangent = T - N * PEVector3::Dot(N, T);
//			adjTangent.Normalize();
//			actor->VertexTangent[Idx] = adjTangent;
//			actor->VertexTangent[Idx].w = PEVector3::Dot(N.Cross(T), tan2[Idx]) < .0f ? -1.0f : 1.0f;
//		}
//
//		delete[] tan1;
//	}
//
//	return true;
//}

bool OBJModel_Loader::BuildTangent(std::vector<OBJModel* >& Objects)
{
	bool bTextureCoordinatesBroken = false;
	for (OBJModel* actor : Objects)
	{
		PracEngLogf("BuildTangent For Model: %s! \n", actor->Name.c_str());
		unsigned int dwTriCount = actor->FaceInfos.size() / 3;
		// clear result
		m_BaseVectors.clear();
		m_TriBaseAssigment.clear();
		m_TriBaseAssigment.reserve(dwTriCount);
		assert(m_BaseVectors.empty());
		assert(m_TriBaseAssigment.empty());
		std::multimap<CBaseIndex, unsigned int, CBaseIndexOrder>		mBaseMap;					// second=index into m_BaseVectors, generated output data
		std::vector<CBase33> vTriangleBase;																					// base vectors per triangle

		// calculate the base vectors per triangle -------------------------------------------
		{
			for (size_t i = 0; i < actor->FaceInfos.size(); i += 3)
			{
				int Idx1 = actor->FaceInfos[i].VertexIdx;
				int Idx2 = actor->FaceInfos[i + 1].VertexIdx;
				int Idx3 = actor->FaceInfos[i + 2].VertexIdx;

				const PEFloat3& v1 = actor->VertexBuffer[Idx1];
				const PEFloat3& v2 = actor->VertexBuffer[Idx2];
				const PEFloat3& v3 = actor->VertexBuffer[Idx3];

				const PEFloat2& w1 = actor->UVs[actor->FaceInfos[i].UVIdx];
				const PEFloat2& w2 = actor->UVs[actor->FaceInfos[i + 1].UVIdx];
				const PEFloat2& w3 = actor->UVs[actor->FaceInfos[i + 2].UVIdx];

				// calculate tangent vectors ---------------------------

				PEVector3 vA(v2.X - v1.X, v2.Y - v1.Y, v2.Z - v1.Z);
				PEVector3 vB(v3.X - v1.X, v3.Y - v1.Y, v3.Z - v1.Z);

				float fDeltaU1 = w2.X - w1.X;
				float fDeltaU2 = w3.X - w1.X;
				float fDeltaV1 = w2.Y - w1.Y;
				float fDeltaV2 = w3.Y - w1.Y;

				float div = (fDeltaU1*fDeltaV2 - fDeltaU2*fDeltaV1);

				if (_isnan(div))
				{
					bTextureCoordinatesBroken = true; div = 0.0f;
				}

				PEVector3 vU, vV, vN = PEVector3::Normalize(PEVector3::Cross(vA, vB));

				if (div != 0.0)
				{
					//	2D triangle area = (u1*v2-u2*v1)/2
					// weight the tangent vectors by the UV triangles area size (fix problems with base UV assignment)
					float fAreaMul2 = fabsf(fDeltaU1*fDeltaV2 - fDeltaU2*fDeltaV1);

					float a = fDeltaV2 / div;
					float b = -fDeltaV1 / div;
					float c = -fDeltaU2 / div;
					float d = fDeltaU1 / div;

					vU = PEVector3::Normalize(vA*a + vB*b)*fAreaMul2;
					vV = PEVector3::Normalize(vA*c + vB*d)*fAreaMul2;
				}
				else
				{
					vU = PEVector3(1, 0, 0); vV = PEVector3(0, 1, 0);
				}

				vTriangleBase.push_back(CBase33(vU, vV, vN));
			}
		}

		// distribute the normals to the vertices
		{
			// we create a new tangent base for every vertex index that has a different normal (later we split further for mirrored use)
			// and sum the base vectors (weighted by angle and mirrored if necessary)
			for (size_t i = 0; i < actor->FaceInfos.size(); i += 3)
			{
				unsigned int e;
				int triIdx = 0;
				CBase33 TriBase = vTriangleBase[triIdx++];
				int Idx1 = actor->FaceInfos[i].VertexIdx;
				int Idx2 = actor->FaceInfos[i + 1].VertexIdx;
				int Idx3 = actor->FaceInfos[i + 2].VertexIdx;

				const PEFloat3& v1 = actor->VertexBuffer[Idx1];
				const PEFloat3& v2 = actor->VertexBuffer[Idx2];
				const PEFloat3& v3 = actor->VertexBuffer[Idx3];
				
				PEVector3 vPos[3] = { PEVector3(v1.X, v1.Y, v1.Z), PEVector3(v2.X, v2.Y, v2.Z), PEVector3(v3.X, v3.Y, v3.Z) };
				int dwPos[3] = { Idx1, Idx2, Idx3 };
				int dwNorm[3] = { actor->FaceInfos[i].UVIdx,
					actor->FaceInfos[i+1].UVIdx,
					actor->FaceInfos[i+2].UVIdx };

				// for each triangle corner
				for (e = 0; e < 3; e++)
				{
					float fWeight = PEVector3::CalcAngleBetween(vPos[(e + 2) % 3] - vPos[e], vPos[(e + 1) % 3] - vPos[e]);			// weight by angle to fix the L-Shape problem

					if (fWeight <= 0.0f)
						fWeight = 0.0001f;

					AddNormal2Base(mBaseMap, dwPos[e], dwNorm[e], TriBase.n*fWeight);
				}
			}
		}

		// distribute the uv vectors to the vertices
		{
			// we create a new tangent base for every vertex index that has a different normal
			// if the base vectors does'nt fit we split as well
			for (size_t i = 0; i < actor->FaceInfos.size(); i += 3)
			{
				unsigned int e;
				CTriBaseIndex Indx;
				int triIdx = 0;
				CBase33 TriBase = vTriangleBase[triIdx++];
				int Idx1 = actor->FaceInfos[i].VertexIdx;
				int Idx2 = actor->FaceInfos[i + 1].VertexIdx;
				int Idx3 = actor->FaceInfos[i + 2].VertexIdx;

				const PEFloat3& v1 = actor->VertexBuffer[Idx1];
				const PEFloat3& v2 = actor->VertexBuffer[Idx2];
				const PEFloat3& v3 = actor->VertexBuffer[Idx3];

				PEVector3 vPos[3] = { PEVector3(v1.X, v1.Y, v1.Z), PEVector3(v2.X, v2.Y, v2.Z), PEVector3(v3.X, v3.Y, v3.Z) };
				int dwPos[3] = { Idx1, Idx2, Idx3 };
				int dwNorm[3] = {actor->FaceInfos[i].UVIdx,
					actor->FaceInfos[i + 1].UVIdx,
					actor->FaceInfos[i + 2].UVIdx };

				// for each triangle corner
				for (e = 0; e < 3; e++)
				{
					float fWeight = PEVector3::CalcAngleBetween(vPos[(e + 2) % 3] - vPos[e], vPos[(e + 1) % 3] - vPos[e]);			// weight by angle to fix the L-Shape problem

					Indx.p[e] = AddUV2Base(mBaseMap, dwPos[e], dwNorm[e], TriBase.u*fWeight, TriBase.v*fWeight, PEVector3::Normalize(TriBase.n));
				}

				m_TriBaseAssigment.push_back(Indx);
			}
		}

		// adjust the base vectors per vertex -------------------------------------------
		{
			std::vector<CBase33>::iterator it;

			for (it = m_BaseVectors.begin(); it != m_BaseVectors.end(); ++it)
			{
				CBase33 &ref = (*it);

				// rotate u and v in n plane
				{
					PEVector3 vUout, vVout, vNout;

					vNout = PEVector3::Normalize(ref.n);

					vUout = ref.u - vNout * (vNout*ref.u);						// project u in n plane
					vVout = ref.v - vNout * (vNout*ref.v);						// project v in n plane

					ref.u = PEVector3::Normalize(vUout); ref.v = PEVector3::Normalize(vVout); ref.n = vNout;

					//assert(ref.u.x>=-1 && ref.u.x<=1);
					//assert(ref.u.y>=-1 && ref.u.y<=1);
					//assert(ref.u.z>=-1 && ref.u.z<=1);
					//assert(ref.v.x>=-1 && ref.v.x<=1);
					//assert(ref.v.y>=-1 && ref.v.y<=1);
					//assert(ref.v.z>=-1 && ref.v.z<=1);
					//assert(ref.n.x>=-1 && ref.n.x<=1);
					//assert(ref.n.y>=-1 && ref.n.y<=1);
					//assert(ref.n.z>=-1 && ref.n.z<=1);
				}
			}


		}

		for (size_t i = 0; i < actor->FaceInfos.size(); i++)
		{
			int Idx = actor->FaceInfos[i].VertexIdx;
			actor->VertexTangent[Idx] = m_BaseVectors[Idx].u;
		}



	}
	return bTextureCoordinatesBroken ? 1 : 0;		// 0=no error
}

OBJModel::OBJModel()
{

}

OBJModel::~OBJModel()
{

}

OBJFaceInfo::OBJFaceInfo(const OBJFaceInfo& obj) :VertexIdx(obj.VertexIdx), NormalIdx(obj.NormalIdx), UVIdx(obj.UVIdx)
{

}

OBJFaceInfo::OBJFaceInfo() : VertexIdx(0), NormalIdx(0), UVIdx(0)
{

}


#pragma warning(pop)


